﻿\subsection{虚拟内存分页}

分页机制为操作系统内存管理提供了强大的支持。
x86 分页机制中，内存虚拟地址占 32 位，每个物理页占 \KiB{4}，页表项占 \Bytes{4}，因此每张页表可容纳 1024 个表项。表示 32 位的虚拟地址空间就需要两级页表，
在 x86 分页机制下被称作页目录（Page Directory）和页表（Page Table）。x86 体系结构中页目录项记作 PDE（Page Directory Entry），页表项记作 PTE（Page Table Entry）。

将页目录记作 \lstinline{pd}，则访问 32 位虚拟地址 \lstinline{vaddr} 的流程如下：

\begin{enumerate}
  \item[（1）] 根据 \lstinline{vaddr[31:22]} 在页目录 \lstinline{pd} 中索引到相应 PDE（\lstinline{pd[vaddr[31:22]]}，记作 \lstinline{pde}），根据物理页框号 \lstinline{pde[31:22]} 访问页表所在的物理页框（记作 \lstinline{pt}）。
  \item[（2）] 根据 \lstinline{vaddr[21:12]} 在 \lstinline{pt} 中索引到相应 PTE（\lstinline{pt[vaddr[21:12]]}，记作 \lstinline{pte}），根据物理页框号 \lstinline{pte[31:22]} 访问实际物理页框（记作 \lstinline{frame}）。
  \item[（3）] 根据 \lstinline{vaddr[11:0]} 指示的页内偏移在物理页框中访问对应的内存单元 \lstinline{frame[vaddr[11:0]]}。
\end{enumerate}

实模式下的 \MiB{1} 地址空间中包含了很多具有特殊用途的内存块，引导加载器和部分操作系统内核也位于这 \MiB{1} 地址空间内。
在开启分页之前，需要先将实模式下的 \MiB{1} 物理地址空间映射到虚拟内存空间。

页目录中的每个 PDE 对应的页表可容纳 1024 个 PTE，每个 PTE 对应 4 KiB 物理页框，因此一个 PDE 可视作对应 \MiB{4} 的地址空间。
本设计中，0 号 PDE 保留（\lstinline{0x0000'0000} \TO \lstinline{0x003F'FFFF}），1 \TO 767 号 PDE 划归用户（\lstinline{0x0040'0000} \TO \lstinline{0xBFFF'FFFF}），
768 \TO 1023 号 PDE 划归内核（\lstinline{0xC000'0000} \TO \lstinline{0xFFFF'FFFF}）。
任何需要通过 0 号页目录对内存的访问操作（如解引用空指针），都被视作异常。
实模式 \MiB{1} 的物理地址空间（共计 256 个页面）全部映射到内核虚拟地址空间，在通过 768 号 PDE 索引的页表予以记录（后文将 $n$ 号 PDE 引用的页表称作 $n$ 号页表）中。

本项目中引导加载器位于物理地址 \lstinline{0x0'0000} \TO \lstinline{0x0'0FFF} 范围内，
为保证分页机制启用后此范围内的物理地址仍能够正确访问，就需要确保处于 \lstinline{0x0000'0000} \TO \lstinline{0x0000'0FFF} 范围内的虚拟地址块
（0 号页目录项、0 号页表项、偏移范围 \lstinline{0x000} \TO \lstinline{0xFFF}）暂时映射到该物理地址空间，操作系统内核调入后，再解除该映射关系。

操作系统中必然涉及到对页目录及页表的直接操作，仅映射普通的物理页面无法在分页开启后访问并修改页目录及页表。因此需要一种方法确保分页方案的自洽性。
通过之前的设计，可以根据 768 号页表中的记录的页目录、768 号页表自身所在的物理页框访问并直接操作页目录、768 号页表本身，但是要直接访问并操作其他页表仍然存在不小的麻烦。
一种办法是将 PDE 的 \lstinline{PDE.PS} 属性位暂时打开，将该 PDE 视作指向 \MiB{4} 大物理页框的页表项；
另一种办法是将 \lstinline{PDE.ADDR} 映射到 768 号页表的空闲 PTE 中，经由 768 号页表间接访问。
前者若操作不当很容易污染其他地址空间或触发异常，后者则需要在 768 号页表中始终预留一个空置的 PTE，且在操作多个页表项时效率很低。因此，还需要一种更好的方案。

从 PDE 及 PTE 的结构上看，页目录和页表并没有太大差别。
页目录自身可以视为特殊的页表，因此，的 PDE 可以指向普通的页表，亦可以指向页目录自身。
当 PDE 指向页目录自身时，中访问的页表仍是页目录自身，PTE 指向的就是各张页表（包括页目录），这样就能够直接操作各张页表（或页目录）。
因此，在页目录中设置一个指向页目录自身的 PDE，就可以实现整个内存分页体系的自洽性（图 \ref{PAGING_SELF_CONSISTENT}）。

\begin{figure}[h!]
    \Centering
    \caption{页目录自映射}
    \label{PAGING_SELF_CONSISTENT}
    \includegraphics[scale=0.6]{build/Paper/Assets/Make-Paging-Scheme-Self-Consistent.pdf}
\end{figure}

内核页目录中设置 0 号、768 号、1022 号、1023 号四个 PDE，PDE \#0 和 PDE \#768 都指向 768 号页表，PDE \#1022 和 PDE \#1023 指向页目录自身。
需要指出，PDE \#1023 在进程设计中将被用于完成进程页目录的自映射。任何进程的页目录中 PDE \#768 \TO PDE \#1022 的内容完全一样，以确保所有的进程共享完全相同的内核地址空间，任何进程中的线程都可以在需要时（如进入内核态运行调度器代码执行线程切换）通过 PDE \#1022 对内核页表进行访问和修改，且修改对于其他进程都是同步的。在设计内存管理和进程、线程模块时将对此进行深入探讨。

经以上讨论，对实模式 \MiB{1} 内存空间分页的详细方案如图 \ref{REAL_MODE_1MiB_MAPPING}。

% \LTXtable{\linewidth}{build/Paper/Assets/Preparation/Real-Mode-1-MiB-Paging-Scheme-in-Protected-Mode.tex}
\begin{longtable}[c]{|*{4}{>{\small\arraybackslash}c|}}
  %\label{table:PT_768} \\
  \caption{实模式 1 MiB 地址空间在保护模式下的映射}\label{REAL_MODE_1MiB_MAPPING}\\
  \cline{1-4}
    \Centering\textbf{页表项} & \Centering\textbf{物理地址} & \Centering\textbf{虚拟地址} & \Centering\textbf{备注} \\
  \hline
  \endfirsthead
  \multicolumn{4}{>{\small}r}{续表} \\
  \hline
    \Centering\textbf{页表项} & \Centering\textbf{物理地址} & \Centering\textbf{虚拟地址} & \Centering\textbf{备注} \\
  \hline
  \endhead
    &  &  & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}IVT, BDA, BootLoader\end{tabular}}} \\*
     ~~0 & \lstinline|0x0'0000| \TO \lstinline|0x0'0FFF| & \lstinline|0xC000'0000| \TO \lstinline|0xC000'0FFF|  & \\*
    &  &  & \\
    \hline

    &  &  & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}EDBA\end{tabular}}} \\*
     ~~1 & \lstinline|0x9'F000| \TO \lstinline|0x9'FFFF| & \lstinline|0xC000'1000| \TO \lstinline|0xC000'1FFF| & \\*
    &  &  & \\
    \hline

    ~~2 & \lstinline|0xA'0000| \TO \lstinline|0xA'0FFF| & \lstinline|0xC000'2000| \TO \lstinline|0xC000'2FFF| & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}Video Display Memory (Color)\end{tabular}}} \\*
    \cline{1-3}
    \vdots & \vdots & \vdots & \\*
    \cline{1-3}
    ~17 & \lstinline|0xA'F000| \TO \lstinline|0xA'FFFF| & \lstinline|0xC001'1000| \TO \lstinline|0xC001'1FFF| & \\
    \hline

    ~18 & \lstinline|0xB'0000| \TO \lstinline|0xB'0FFF| & \lstinline|0xC001'2000| \TO \lstinline|0xC001'2FFF| & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}Video Display Memory (Black \& White)\end{tabular}}} \\*
    \cline{1-3}
    \vdots & \vdots & \vdots & \\*
    \cline{1-3}
    ~25 & \lstinline|0xB'7000| \TO \lstinline|0xB'7FFF| & \lstinline|0xC001'9000| \TO \lstinline|0xC001'9FFF| & \\
    \hline

    ~26 & \lstinline|0xB'8000| \TO \lstinline|0xB'8FF|F & \lstinline|0xC001'A000| \TO \lstinline|0xC001'AFFF| & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}Video Display Memory (Text-Mode)\end{tabular}}}\\*
    \cline{1-3}
    \vdots & \vdots & \vdots & \\*
    \cline{1-3}
    ~33 & \lstinline|0xB'F000| \TO \lstinline|0xB'FFFF| & \lstinline|0xC002'1000| \TO \lstinline|0xC002'1FFF| & \\
    \hline

    ~34 & \lstinline|0xC'0000| \TO \lstinline|0xC'0FFF| & \lstinline|0xC002'2000| \TO \lstinline|0xC002'2FFF| & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}Video BIOS\end{tabular}}} \\*
    \cline{1-3}
    \vdots & \vdots & \vdots & \\*
    \cline{1-3}
    ~41 & \lstinline|0xC'7000| \TO \lstinline|0xC'7FFF| & \lstinline|0xC002'9000| \TO \lstinline|0xC002'9FFF| & \\
    \hline

    ~42 & \lstinline|0xC'8000| \TO \lstinline|0xC'8FFF| & \lstinline|0xC002'A000| \TO \lstinline|0xC002'AFFF| & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}ROM, Hardware-Mapped RAM\end{tabular}}} \\*
    \cline{1-3}
    \vdots & \vdots & \vdots & \\*
    \cline{1-3}
    ~81 & \lstinline|0xE'F000| \TO \lstinline|0xE'FFFF| & \lstinline|0xC005'1000| \TO \lstinline|0xC005'1FFF| & \\
    \hline

    ~82 & \lstinline|0xF'0000| \TO \lstinline|0xF'0FFF| & \lstinline|0xC005'2000| \TO \lstinline|0xC005'2FFF| & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}BIOS\end{tabular}}} \\*
    \cline{1-3}
    \vdots & \vdots & \vdots & \\*
    \cline{1-3}
    ~97 & \lstinline|0xF'F000| \TO \lstinline|0xF'FFFF| & \lstinline|0xC006'1000| \TO \lstinline|0xC006'1FFF| & \\
    \hline

    &  &  & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}Memory Address Map, GDT\end{tabular}}} \\*
     ~98 & \lstinline|0x0'1000| \TO \lstinline|0x0'1FFF| & \lstinline|0xC006'2000| \TO \lstinline|0xC006'2FFF| & \\*
    &  &  & \\
    \hline

    &  &  & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}Page Directory\end{tabular}}} \\*
     ~99 & \lstinline|0x0'2000| \TO \lstinline|0x0'2FFF| & \lstinline|0xC006'3000| \TO \lstinline|0xC006'3FFF| & \\*
    &  &  & \\
    \hline

    &  &  & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}Page Table \#768 (Current Page)\end{tabular}}} \\*
     100 & \lstinline|0x0'3000| \TO \lstinline|0x0'3FFF| & \lstinline|0xC006'4000| \TO \lstinline|0xC006'4FFF| & \\*
    &  &  & \\
    \hline
    
    101 & \lstinline|0x0'4000| \TO \lstinline|0x0'4FFF| & \lstinline|0xC006'5000| \TO \lstinline|0xC006'5FFF| & \multirow{3}{*}{{\begin{tabular}{>{\RaggedRight\zihao{6}\arraybackslash}p{4cm}}OS Kernel\end{tabular}}} \\*
    \cline{1-3}
    \vdots & \vdots & \vdots & \\*
    \cline{1-3}
    255 & \lstinline|0x9'E000| \TO \lstinline|0x9'EFFF| & \lstinline|0xC00F'F000| \TO  \lstinline|0xC00F'FFFF| & \\
    \hline
  \end{longtable}

页目录和页表都设置完毕后，在开启分页前，还有一个重要的细节需要处理。
分页模式下，所有的地址都是虚拟地址，对于 \lstinline{GDTR} 中保存的地址也是如此。
因此，需要使用 \lstinline{lgdt} 指令重新设置 GDT 的位置，对于 GDT 中的全局描述符，段的物理地址也需要修改成虚拟地址。
该过程处理完毕后，加载页目录物理地址到 \lstinline{CR3} 寄存器，然后打开 \lstinline{CR0.PG} 启用分页模式。
